Skip to content

feat: per-stem confidence in Session Musician note draft - #28

Merged
slittycode merged 1 commit into
mainfrom
claude/per-stem-confidence
May 13, 2026
Merged

feat: per-stem confidence in Session Musician note draft#28
slittycode merged 1 commit into
mainfrom
claude/per-stem-confidence

Conversation

@slittycode

Copy link
Copy Markdown
Owner

Successor to #23 (auto-closed when its base branch claude/thirsty-easley-faeaea was deleted after PR #22 merged). Branch rebased onto main; content identical.

Why

Bass and lead stems often have very different transcription confidence — bass might be Solid (0.85) while the lead is Rough (0.4). PR #22 collapses both into a single combined band, which hides that. When a producer toggles BASS or OTHER in the Stem note draft block, the confidence pill should reflect that stem's mean confidence; the "All" state keeps showing the overall.

What changed

Backend

  • New pure helper _per_stem_average_confidence in analyze_transcription.py computes mean confidence per stemSource.
  • TorchcrepeBackend.transcribe emits perStemAverageConfidence: Record<string, number> on both populated and empty result paths. Returns {} when fullMixFallback is true.
  • JSON_SCHEMA.md documents the new field.

Frontend

  • Optional perStemAverageConfidence on TranscriptionDetail in types/measurement.ts.
  • New pure helper selectNoteDraftBandConfidence in renderState.ts picks the right value based on (transcriptionDetail, stemFilter, renderState).
  • NoteDraftBlock.tsx uses the helper to drive the band's confidence prop.
  • Bugfix: both analysisRunsClient.ts and backendPhase1Client.ts rebuild transcriptionDetail from an explicit field list and were silently dropping any new fields. Added matching parsePerStemAverageConfidence in each.

Tests

  • test_analyze.py — pure-helper coverage (4 tests).
  • test_server.py — end-to-end round-trip catching any future whitelist trimming.
  • renderState.test.tsselectNoteDraftBandConfidence matrix (7 tests).
  • upload-phase1-midi.spec.ts — clicks BASS → OTHER → All and asserts the pill text changes.

364 backend tests + 46 smoke tests pass.

🤖 Generated with Claude Code

Bass and lead stems often have very different transcription confidence — bass
might be Solid (0.85) while other is Rough (0.4). Collapsing them into one
combined band hides that. When the producer toggles the BASS / OTHER button
in Block A, the confidence pill now reflects that stem's mean confidence; the
"All" state keeps showing the overall.

Backend
- analyze_transcription.py: new pure helper `_per_stem_average_confidence`
  computes the mean confidence per `stemSource` over the surviving
  post-dedup, post-cap notes. `TorchcrepeBackend.transcribe` emits the new
  `perStemAverageConfidence: Record<string, number>` field on both the
  populated and empty result paths. Returns `{}` when `fullMixFallback` is
  true so the UI doesn't show a per-stem band where there's no meaningful
  separation to report.
- JSON_SCHEMA.md: documents the new field.
- analyze.py: re-exports the helper so test_analyze can reach it via the
  same module-loading pattern as the other transcription helpers.
- No changes to server.py or analysis_runtime — the field flows through the
  existing transcriptionDetail passthrough automatically (confirmed by the
  new round-trip server test).

Frontend
- measurement.ts: adds optional `perStemAverageConfidence` to
  TranscriptionDetail. Optional because legacy snapshots and the empty-notes
  path may omit it.
- renderState.ts: new pure helper `selectNoteDraftBandConfidence` picks the
  right confidence value based on (transcriptionDetail, stemFilter,
  renderState). Falls back to the overall when stem filter is null, the
  per-stem field is missing, the selected stem key isn't keyed, or the value
  is non-finite. Returns the overall in full-mix-fallback / legacy render
  states since those replace the band copy entirely.
- NoteDraftBlock.tsx: uses the helper to drive the band confidence prop.
- analysisRunsClient.ts and backendPhase1Client.ts: both rebuild
  transcriptionDetail from an explicit field list and were silently dropping
  the new field. Added `parsePerStemAverageConfidence` to each — clamps to
  0-1 in the legacy path and skips non-finite entries in both.

Tests
- test_analyze.py: pure-helper coverage for `_per_stem_average_confidence`
  (groups means by stem, handles empty notes, skips missing/empty/null
  stemSource, tolerates non-numeric confidence values).
- test_server.py: end-to-end round-trip — subprocess emits
  perStemAverageConfidence, runtime persists it, run snapshot returns it
  unchanged. Catches any future whitelisting in the worker path.
- renderState.test.ts: `selectNoteDraftBandConfidence` matrix (overall fallback,
  bass stem, other stem, missing field, missing key, non-finite value, plus
  the two override render states).
- upload-phase1-midi.spec.ts: stubs `perStemAverageConfidence: {bass: 0.92,
  other: 0.32}` and clicks BASS → OTHER → All, asserting the pill text
  changes "Solid scaffold · 92%" → "Rough sketch · 32%" → "Solid scaffold ·
  83%" (the overall).

Targets PR #22 (claude/thirsty-easley-faeaea) as the base; will retarget to
main after that lands. Plan in
~/.claude/plans/re-evaluate-asa-s-session-musician-sharded-balloon.md
("Out-of-scope follow-ups" section).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@slittycode
slittycode merged commit e5e7486 into main May 13, 2026
2 checks passed
@slittycode
slittycode deleted the claude/per-stem-confidence branch May 13, 2026 00:42

@slittycode slittycode left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE

Summary

Additive Phase 1 schema extension: perStemAverageConfidence is computed at transcription time from surviving post-dedup notes, emitted in both the populated and empty result paths, documented in JSON_SCHEMA.md, and propagated read-only through both client parsers to a pure selector that drives the confidence pill in NoteDraftBlock. The PR also fixes the silent field-dropping bug in both client parsers. Test coverage is solid: 4 backend unit tests, 1 server round-trip, 7 frontend unit tests, and a smoke test that clicks through the BASS → OTHER → All toggle sequence. (Could not run test suites locally — venv and npm packages not installed in this workspace; PR description reports 364 backend + 46 smoke passing.)

Findings

Worth considering

  • clamp01 asymmetry between parsers (analysisRunsClient.ts vs backendPhase1Client.ts): backendPhase1Client.ts clamps per-stem values to [0, 1] via clamp01() for defense against backend payload bugs; analysisRunsClient.ts does not. Both parse the same backend output, so it's harmless today, but if the backend ever emits an out-of-range float through the runs path, the two display paths will diverge. Worth making them consistent.

  • NaN/Infinity not guarded in _per_stem_average_confidence (analyze_transcription.py): The try/except (TypeError, ValueError) block correctly drops non-numeric strings and None, but a Python float('nan') or float('inf') silently passes through and would propagate into the JSON via np.mean. torchcrepe output is bounded to [0, 1] so this is a remote edge case, not a current risk. A math.isfinite(conf) guard after the cast would close it cheaply.

Test results

Could not execute locally (venv/npm not installed in this workspace). PR-reported: 364 backend, 46 smoke — all passing.

Phase boundary check

Clean. perStemAverageConfidence is computed inside TorchcrepeBackend.transcribe (Phase 1), flows through both client parsers as read-only data, and is consumed by a pure selector in the UI. No Phase 2 involvement, no Phase 1 mutation downstream.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant